#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

#include <psp2/io/fcntl.h>

#include "cheat_list.h"
#include "linked_list.h"
#include "config_list.h"
#include "utils_string.h"
#include "file.h"
#include "config.h"

static void freeCheatValue(CheatValue *values, int n_values)
{
    if (values)
    {
        int i;
        for (i = 0; i < n_values; i++)
        {
            if (values[i].label)
                free(values[i].label);
            if (values[i].codes)
                free(values[i].codes);
        }
        free(values);
    }
}

static void freeEntryData(void *data)
{
    CheatEntryData *e_data = (CheatEntryData *)data;
    if (e_data)
    {
        if (e_data->desc)
            free(e_data->desc);
        if (e_data->values)
            freeCheatValue(e_data->values, e_data->n_values);
        free(e_data);
    }
}

static int getCheatValue(char *str, CheatValue **values, int *n_values, int *max_values)
{
    CheatValue *value;
    CheatCode *code;
    int max_codes;
    int len, i;
    char *head = str;
    char *tail = str + strlen(str);
    char *p, *p2, *next;

    p2 = strchr(head, ';'); // 查找第一组: 名称和执行次数
    if (!p2)
        return 0;

    if (!*values) // 内存未分配
    {
        *values = calloc(8, sizeof(CheatValue)); // 预分配8个
        if (!*values)
            return 0;
        *max_values = 8; // 记录最大values个数
        *n_values = 0;   // 记录当前values个数
    }

    if (*n_values >= *max_values) // 内存需扩展
    {
        void *tmp = realloc(*values, (*max_values + 8) * sizeof(CheatValue)); // 扩展8个
        if (!tmp)
            return 0;
        *values = tmp;
        memset((*values) + *max_values, 0, 8 * sizeof(CheatValue)); // 将扩展出的内存块置0
        (*max_values) += 8;                                         // 记录最大values个数
    }

    value = &(*values)[*n_values];

    p = strnchr(head, p2 - head, ','); // 查找max_times
    if (!p)
        p = p2;

    len = p - head;
    value->label = malloc(len + 1);
    if (!value->label)
        return 0;
    strncpy(value->label, head, len);
    (value->label)[len] = '\0';

    if (++p < p2)                              // 如果max_times不为空
        value->max_times = StringToDecimal(p); // max_times执行次数，0为无数次

    // printf("value_%d: name=%s, max_times=%d\n", *n_values, value->label, value->max_times);
    (*n_values)++; // 记录当前values个数

    head = (p2 + 1);
    while (*head == ' ' || *head == '\t')
        head++;

    while ((*(tail - 1) == ' ' || *(tail - 1) == '\t') && tail > head)
        tail--;

    if (head >= tail) // 代码为空
        return 1;

    len = tail - head;
    value->native = malloc(len + 1); // 保存原生代码
    if (value->native)
    {
        strcpy(value->native, head);
        // printf("  native=%s\n", value->native);
    }

    p = p2 = head;
    max_codes = 0;
    while (p < tail)
    {
        p2 = strchr(p, ';'); // 查找下一组代码
        if (!p2)
            p2 = tail;
        max_codes++;
        p = p2 + 1;
    }

    if (max_codes == 0) // 如果代码组总数量为0，返回
        return 1;

    value->codes = calloc(max_codes, sizeof(CheatCode)); // 分配codes内存
    if (!value->codes)
        return -1;

    p = p2 = head;
    for (i = 0; i < max_codes && p < tail; i++)
    {
        code = &value->codes[value->n_codes];
        next = strchr(p, ';'); // 查找下一组
        if (!next)
            next = tail;

        code->address = StringToHexdecimal(p); // 十六进制address

        p2 = strnchr(p, next - p, ':'); // 查找value分割符
        if (!p2 || p2 + 1 >= next)      // 如果失败，尝试读取下一组
            goto NEXT;
        p = p2 + 1;
        code->value = StringToHexdecimal(p); // 十六进制value

        p2 = strnchr(p, next - p, '&'); // 查找mask分割符
        if (!p2)
            p2 = next;

        int bytes = p2 - p;
        if (bytes <= 2) // 2位十六进制为1字节
            code->bytes = 1;
        else if (bytes <= 4) // 4位十六进制为2字节
            code->bytes = 2;
        else if (bytes <= 8) // 8位十六进制为4字节
            code->bytes = 4;
        else
            goto NEXT;

        if (p2 != next && p2 + 1 < next)
        {
            p = p2 + 1;
            code->mask = StringToHexdecimal(p); // 十六进制mask
        }
        else // 没有mask参数，则按照字节数使用默认的mask
        {
            if (code->bytes == 1) // 1字节
                code->mask = 0xFF;
            else if (code->bytes == 2) // 2字节
                code->mask = 0xFFFF;
            else if (code->bytes == 4) // 4字节
                code->mask = 0xFFFFFFFF;
            else
                goto NEXT;
        }

        value->n_codes++; // 匹配成功，实际数量加1

    NEXT:
        // printf("  code_%d: address=0x%X, value=0x%X, bytes=%u, mask=0x%X\n", i, code->address, code->value, code->bytes, code->mask);
        p = next + 1;
    }

    if (value->n_codes == 0) // 如果为空则释放
    {
        free(value->codes);
        value->codes = NULL;
    }
    else if (value->n_codes < max_codes) // 如果实际读取小于预判断，realloc节省内存
    {
        void *tmp = realloc(value->codes, value->n_codes * sizeof(CheatCode));
        if (tmp)
            value->codes = tmp;
    }

    return 1;
}

static void getEntryData(LinkedList *list, CheatEntryData **data, char *section, char *key, char *value)
{
    static int max_values = 0;

    if (section)
    {
        if (*data)
        {
            if ((*data)->values && (*data)->n_values < max_values)
            {
                void *tmp = realloc((*data)->values, (*data)->n_values * sizeof(CheatValue));
                if (tmp)
                {
                    // printf("realloc values: %d==>%d, %p==>%p\n", max_values, (*data)->n_values, (*data)->values, tmp);
                    (*data)->values = tmp;
                    max_values = (*data)->n_values;
                }
            }
        }

        *data = (CheatEntryData *)calloc(1, sizeof(CheatEntryData));
        if (*data)
        {
            // printf("cheat: name=%s\n", section);
            LinkedListAdd(list, *data);
            (*data)->desc = section;
            section = NULL;
            max_values = 0;
        }
    }
    else if (*data)
    {
        if (strcmp(key, "handler") == 0)
        {
            (*data)->handler = StringToDecimal(value);
        }
        else if (strcmp(key, "endian") == 0)
        {
            (*data)->endian = StringToDecimal(value);
        }
        else if (strcmp(key, "code") == 0)
        {
            getCheatValue(value, &(*data)->values, &(*data)->n_values, &max_values);
        }
    }

    if (section)
        free(section);
    if (key)
        free(key);
    if (value)
        free(value);
}

int CheatListGetEntriesFromBuffer(LinkedList *list, void *buffer, int size)
{
    if (!list)
        return -1;

    int res = 0;
    char *p = buffer;

    // Skip UTF-8 bom
    uint32_t bom = 0xBFBBEF;
    if (memcmp(p, &bom, 3) == 0)
    {
        p += 3;
        size -= 3;
    }

    CheatEntryData *data = NULL;
    char *line = NULL;
    char *section = NULL;
    char *key = NULL;
    char *value = NULL;

    do
    {
        res = StringGetLine(p, size, &line);
        if (res > 0)
        {
            if (line)
            {
                // printf("GetLine: %s\n", line);
                if (StringParseLine(line, &section, &key, &value))
                {
                    // printf("ReadLine: section=%s, key=%s, value=%s\n", section ? section : "null", key ? key : "null", value ? value : "null");
                    getEntryData(list, &data, section, key, value);
                }
                free(line);
                line = NULL;
                section = NULL;
                key = NULL;
                value = NULL;
            }
            size -= res;
            p += res;
        }
    } while (res > 0);

    return 0;
}

int CheatListGetEntries(LinkedList *list, const char *path)
{
    if (!list)
        return -1;

    void *buffer = NULL;
    size_t size = 0;
    if (AllocateReadFile(path, &buffer, &size) < 0)
        return -1;

    CheatListGetEntriesFromBuffer(list, buffer, size);

    free(buffer);

    return 0;
}

LinkedList *NewCheatList()
{
    LinkedList *list = NewLinkedList();
    if (!list)
        return NULL;

    LinkedListSetFreeEntryDataCallback(list, freeEntryData);
    return list;
}

int CheatListUpdateConfig(LinkedList *list)
{
    if (!list)
        return -1;

    LinkedListEntry *entry = LinkedListHead(list);

    while (entry)
    {
        CheatEntryData *data = (CheatEntryData *)LinkedListGetEntryData(entry);
        if (data->old_select != data->select)
        {
            data->cur_times = 0;
            data->old_select = data->select;
        }
        entry = LinkedListNext(entry);
    }

    return 0;
}

int CheatListResetConfig(LinkedList *list)
{
    if (!list)
        return -1;

    LinkedListEntry *entry = LinkedListHead(list);

    while (entry)
    {
        CheatEntryData *data = (CheatEntryData *)LinkedListGetEntryData(entry);
        data->select = 0;
        if (data->old_select != data->select)
        {
            data->cur_times = 0;
            data->old_select = data->select;
        }
        entry = LinkedListNext(entry);
    }

    return 0;
}

int CheatListLoadConfig(LinkedList *list, const char *path)
{
    if (!list)
        return -1;

    LinkedList *config_list = NewConfigList();
    if (config_list)
        ConfigListGetEntries(config_list, path);

    LinkedListEntry *entry = LinkedListHead(list);

    while (entry)
    {
        CheatEntryData *data = (CheatEntryData *)LinkedListGetEntryData(entry);
        data->select = 0;

        if (data->desc && config_list)
        {
            LinkedListEntry *find = ConfigListFindEntryByKey(config_list, data->desc);
            if (find)
            {
                ConfigEntryData *c_data = (ConfigEntryData *)LinkedListGetEntryData(find);
                data->select = StringToDecimal(c_data->value);
                if (data->old_select != data->select)
                {
                    data->cur_times = 0;
                    data->old_select = data->select;
                }
                LinkedListRemove(config_list, find);
            }
        }

        entry = LinkedListNext(entry);
    }

    if (config_list)
        LinkedListDestroy(config_list);

    return 0;
}

int CheatListSaveConfig(LinkedList *list, const char *path)
{
    if (!list)
        return -1;

    int fd = sceIoOpen(path, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, 0777);
    if (fd < 0)
        return fd;

    int ret = 0;
    char string[MAX_CONFIG_LINE_LENGTH];
    LinkedListEntry *entry = LinkedListHead(list);

    while (entry)
    {
        CheatEntryData *data = (CheatEntryData *)LinkedListGetEntryData(entry);
        snprintf(string, MAX_CONFIG_LINE_LENGTH, "%s = \"%d\"\n", data->desc, data->select);

        if ((ret = sceIoWrite(fd, string, strlen(string))) < 0)
            break;

        entry = LinkedListNext(entry);
    }

    sceIoClose(fd);
    if (ret < 0)
        sceIoRemove(path);
    return ret;
}
